#include "client/client.hpp"

void client::initialize() {
    win::initialize_socket_library();
    _initialized = true;
}

bool client::send(std::vector<char> buf) {
    if (!_connected) {
        return false;
    }
    std::scoped_lock<std::mutex> lock(_m);
    auto res = ::send(_socket, buf.data(), static_cast<int>(buf.size()), 0);
    if (res == SOCKET_ERROR) {
        lwarn("server disconnected.");
        close();
        return false;
    }
    return true;
}

bool client::connect(const std::string& address, u_short port, recv_hook_t recv_hook) {
    if (!_initialized) {
        initialize();
    }
    _initialize_socket();
    win::sockaddr_in addr;
    addr.sin_family = AF_INET;
    inet_pton(AF_INET, address.data(), &addr.sin_addr.s_addr);
    addr.sin_port = htons(port);

    if (::connect(_socket, reinterpret_cast<LPSOCKADDR>(&addr), sizeof(addr)) == SOCKET_ERROR) {
        lerror("socket connect failed. " << win::get_last_error_message());
        return false;
    }

    _connected = true;

    if (recv_hook) {
        _recv_hook = std::move(recv_hook);
    }
    _close_flag.store(true);
    _recv_thread = std::thread([this]() {
        while (_close_flag.load()) {
            std::vector<char> buf(4096, 0);
            auto ret = ::recv(_socket, buf.data(), static_cast<int>(buf.size()), 0);
            if (ret == -1) {
                if (_connected) {
                    lwarn("server shutdown unexpectly");
                    close();
                }
                break;
            }
            if (ret == 0) {
                lwarn("server closed.");
                close();
                break;
            }
            buf.resize(ret);
            if (!_recv_hook(std::move(buf))) {
                break;
            }
        }
        linfo("receive thread closed");
    });

    return true;
}

void client::close() {
    if (!_connected) {
        return;
    }
    ::closesocket(_socket);
    _socket = INVALID_SOCKET;
    _close_flag.store(false);
    _connected = false;
    if (_recv_thread.get_id() != std::this_thread::get_id()) {
        _recv_thread.join();
    }
    _close_hook();
}

void client::_initialize_socket() {
    _socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    assert(_socket != INVALID_SOCKET, "create socket failed.");
    linfo("client socket created.");
}
